﻿namespace ShellNavigator.Client.Core
{
  #region

  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Linq;
  using System.Threading;

  using ShellNavigator.Client.Forms;
  using ShellNavigator.Client.Models;
  using ShellNavigator.Client.Utils;

  using FileInfo = ShellNavigator.Client.Models.FileInfo;

  #endregion

  public class SearchEngine
  {
    #region Constants and Fields

    private readonly Request request;

    #endregion

    #region Constructors and Destructors

    public SearchEngine(Request request)
    {
      this.request = request;
      Validate(this.request);
    }

    #endregion

    #region Public Methods

    public static void Validate(Request request)
    {
      if (string.IsNullOrEmpty(request.Filemask))
      {
        request.Filemask = "*.*";
      }

      if (string.IsNullOrEmpty(request.Directory))
      {
        try
        {
          request.Directory = Path.GetDirectoryName(request.Filemask);
          request.Filemask = Path.GetFileName(request.Filemask);
        }
        catch
        {
          throw new ArgumentException("Can't extract directory from filemask. Specify directory explicitly");
        }

        if (string.IsNullOrEmpty(request.Directory))
        {
          throw new ArgumentException("Can't extract directory from filemask. Specify directory explicitly");
        }
      }

      if (!Directory.Exists(request.Directory))
      {
        throw new ArgumentException("Directory doesn't exists: " + request.Directory);
      }
    }

    public static void VisualSearch(
      Request request, Action<Request, IEnumerable<InfoBase>> onSuccess, Action<Exception> onError)
    {
      try
      {
        var searchEngine = new SearchEngine(request);

        UIHelper.LongOperationWithCancel(
          request, searchEngine.Search, onSuccess, onError, new WaitWindow());
      }
      catch (Exception ex)
      {
        onError(ex);
      }
    }

    #endregion

    #region Methods

    private bool FilterByText(string textLine)
    {
      var textFound = !string.IsNullOrEmpty(textLine);
      var isFilter = !string.IsNullOrEmpty(this.request.Text);

      return textFound == isFilter;
    }

    private IEnumerable<FileInfo> FindAllFiles(CancellationToken cancelToken)
    {
      var includeList = new List<string>();
      var excludeList = new List<string>();

      if (!string.IsNullOrEmpty(this.request.Text))
      {
        excludeList.AddRange(FileInfo.ExcludeExtensions);
      }

      MaskHelper.ParseMask(this.request.Filemask, includeList, excludeList);

      var files = from searchPattern in includeList
                  from file in
                    DirectoryHelper.EnumerateFiles(this.request.Directory, searchPattern, SearchOption.AllDirectories, cancelToken)
                  where !excludeList.Any(excludePattern => PatternMatcher.StrictMatchPattern(excludePattern, file))
                  let textLine =
                    FilterHelper.TextMatch(file, this.request.Text, this.request.IsPlainText, this.request.IsMatchCase)
                  where this.FilterByText(textLine)
                  select new FileInfo { Name = file, TextLine = textLine };
      return files.ToList();
    }

    private IEnumerable<ProjectInfo> FindAllProjects(IEnumerable<FileInfo> files, CancellationToken cancelToken)
    {
      var projects = from file in files
                     from instance in DirectoryHelper.EnumerateProjects(file, cancelToken)
                     group instance by instance.Name
                     into project
                     select new ProjectInfo(project.SelectMany(i => i.Files).ToList()) { Name = project.Key };

      return projects.ToList();
    }

    private IEnumerable<SolutionInfo> FindAllSolutions(IEnumerable<ProjectInfo> projects, CancellationToken cancelToken)
    {
      var solutions = from project in projects
                      from instance in DirectoryHelper.EnumerateSolutions(project, cancelToken)
                      group instance by instance.Name
                        into solution
                        select
                          new SolutionInfo(solution.SelectMany(i => i.Projects).ToList()) { Name = solution.Key };

      return solutions.ToList();
    }

    private IEnumerable<InfoBase> Search(CancellationToken cancelToken)
    {
      var files = this.FindAllFiles(cancelToken);

      if (this.request.Target == AppTargets.File)
      {
        return files;
      }

      var projects = this.FindAllProjects(files, cancelToken);

      if (this.request.Target == AppTargets.Project)
      {
        return projects;
      }

      var solutions = this.FindAllSolutions(projects, cancelToken);

      if (this.request.Target == AppTargets.Solution)
      {
        return solutions;
      }

      throw new ArgumentException("Can't process search - no target: " + this.request.Target.ToString());
    }

    #endregion
  }
}